package com.zys.http.processor.spring;

import cn.hutool.core.text.CharSequenceUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.tree.MethodNodeData;
import com.zys.http.entity.velocity.MethodItem;
import com.zys.http.extension.setting.HttpSetting;
import com.zys.http.processor.AbstractJavaApiProcessor;
import com.zys.http.tool.UrlTool;
import com.zys.http.tool.VelocityTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * @author zhou ys
 * @since 2024-05-16
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SpringJavaApiProcessor extends AbstractJavaApiProcessor {
    public static final SpringJavaApiProcessor INSTANCE = new SpringJavaApiProcessor();

    @Override
    @SneakyThrows
    @SuppressWarnings("deprecation")
    public List<? extends PsiElement> controllers(Project project, Module module) {
        Optional<GlobalSearchScope> scopeOp = Optional.of(module).map(Module::getModuleScope);
        Collection<PsiAnnotation> annotations = ApplicationManager.getApplication().runReadAction((Computable<Collection<PsiAnnotation>>) () -> {
            Collection<PsiAnnotation> all = new ArrayList<>();
            for (SpringEnums.Controller controller : SpringEnums.Controller.values()) {
                if (controller.equals(SpringEnums.Controller.RESPONSE_BODY)) {
                    continue;
                }
                String shortClassName = controller.getShortClassName();
                all.addAll(scopeOp.map(ms -> JavaAnnotationIndex.getInstance().get(shortClassName, project, ms)).orElse(new ArrayList<>()));
            }

            String customAnno = HttpSetting.getInstance().getCustomAnno();
            if (CharSequenceUtil.isNotEmpty(customAnno) && !customAnno.endsWith(".")) {
                String shortName = customAnno.substring(customAnno.lastIndexOf('.') + 1);
                all.addAll(scopeOp.map(ms -> JavaAnnotationIndex.getInstance().get(shortName, project, ms)).orElse(new ArrayList<>()));
            }
            return all;
        });

        return annotations.stream()
                .map(PsiElement::getParent)
                .map(PsiModifierList.class::cast)
                .map(PsiModifierList::getParent)
                .filter(PsiClass.class::isInstance)
                .map(PsiClass.class::cast)
                .toList();
    }

    @Override
    @SneakyThrows
    public String controllerPath(PsiElement psiElement) {
        if (!(psiElement instanceof PsiClass psiClass)) {
            return "";
        }

        PsiModifierList modifierList = psiClass.getModifierList();
        if (Objects.isNull(modifierList)) {
            return "";
        }

        PsiAnnotation[] annotations = modifierList.getAnnotations();

        for (PsiAnnotation annotation : annotations) {
            if (SpringEnums.Method.REQUEST.getClazz().equals(annotation.getQualifiedName())) {
                return annotationValue(annotation, new String[]{VALUE_KEY, "path"});
            }
        }
        return "";
    }

    @Override
    public String methodPath(PsiElement element) {
        if (!(element instanceof PsiMethod method)) {
            return null;
        }
        PsiAnnotation[] annotations = method.getAnnotations();

        for (PsiAnnotation annotation : annotations) {
            if (!SpringEnums.Method.contains(annotation)) {
                continue;
            }
            return annotationValue(annotation, new String[]{VALUE_KEY, "path"});
        }
        return null;
    }

    @Override
    protected MethodNode createMethodNode(PsiMethod method, String controllerPath, String contextPath) {
        PsiAnnotation[] annotations = method.getAnnotations();

        for (PsiAnnotation annotation : annotations) {
            if (!SpringEnums.Method.contains(annotation)) {
                continue;
            }
            HttpEnum.HttpMethod httpMethod = SpringEnums.Method.get(annotation);
            String name = annotationValue(annotation, new String[]{VALUE_KEY, "path"});
            MethodNodeData data = new MethodNodeData(httpMethod, name, controllerPath, contextPath);
            data.setDescription(methodSwagger(method));
            data.setPsiElement(method);
            return new MethodNode(data);
        }
        return null;
    }

    @Override
    protected void parameterParse(@NotNull PsiParameter parameter, @NotNull Map<String, ParamProperty> map,
                                  boolean isJsonPretty) {
        String parameterName = parameter.getName();
        PsiType parameterType = parameter.getType();

        String[] keys = new String[]{VALUE_KEY, NAME_KEY};
        if (parameter.getAnnotation(SpringEnums.Param.REQUEST_HEADER.getClazz()) != null) {
            // 如果参数注解带 @RequestHeader, 默认 String, 不作特殊处理
            ParamProperty property = map.getOrDefault(REQUEST_TYPE_KEY, null);
            if (property == null) {
                map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            }
            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.HEADER));
            return;
        }
        HttpEnum.ParamUsage paramUsage = HttpEnum.ParamUsage.URL;
        PsiAnnotation requestParamAnno = parameter.getAnnotation(SpringEnums.Param.REQUEST_PARAM.getClazz());

        if (Objects.nonNull(requestParamAnno)) {
            // @RequestParam 是否有 value 或 name 属性, 如果有会将请求参数名变为那个
            String annotationValue = annotationValue(requestParamAnno, keys);
            if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                parameterName = annotationValue;
            }
        }

        String canonicalText = parameterType.getCanonicalText();
        if (canonicalText.contains("org.springframework.web.multipart.MultipartFile")) {
            // 说明是文件
            PsiAnnotation requestPartAnno = parameter.getAnnotation(SpringEnums.Param.REQUEST_PART.getClazz());
            if (Objects.nonNull(requestPartAnno)) {
                // @RequestPart 是否有 value 或 name 属性, 如果有会将请求参数名变为那个
                String annotationValue = annotationValue(requestPartAnno, keys);
                if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                    parameterName = annotationValue;
                }
            }

            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.FILE));
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.MULTIPART_FORM_DATA, HttpEnum.ParamUsage.HEADER));
            return;
        }

        PsiAnnotation pathVariableAnno = parameter.getAnnotation(SpringEnums.Param.PATH_VARIABLE.getClazz());
        if (Objects.nonNull(pathVariableAnno)) {
            // @PathVariable 是否有 value 或 name 属性, 如果有会将请求参数名变为那个
            String annotationValue = annotationValue(pathVariableAnno, keys);
            paramUsage = HttpEnum.ParamUsage.PATH;
            if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                parameterName = annotationValue;
            }
        }

        Object paramDefaultTypeValue = typeDefaultValue(parameter.getProject(), parameterType);
        if (Objects.isNull(paramDefaultTypeValue)) {
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return;
        }

        boolean isJson = parameter.hasAnnotation(SpringEnums.Param.REQUEST_BODY.getClazz());
        parameterParseResult(parameterName, paramDefaultTypeValue, isJson, isJsonPretty, map, paramUsage);
    }

    @Override
    protected MethodItem postmanMethodItem(PsiMethod method, String contextPath, String controllerPath) {
        PsiAnnotation[] annotations = method.getAnnotations();
        MethodItem item = new MethodItem();
        // 方法名
        String methodSwagger = methodSwagger(method);
        methodSwagger = CharSequenceUtil.isEmpty(methodSwagger) ? method.getName() : methodSwagger;
        item.setName(methodSwagger);

        // 请求方式、请求uri、请求头类型
        for (PsiAnnotation annotation : annotations) {
            HttpEnum.HttpMethod httpMethod = SpringEnums.Method.get(annotation);
            if (Objects.isNull(httpMethod)) {
                continue;
            }
            item.setMethod(httpMethod.name());

            // 请求 uri
            String path = annotationValue(annotation, new String[]{VALUE_KEY, "path"});
            item.setUri(UrlTool.buildMethodUri(contextPath, controllerPath, path));

            // 请求头类型

            // 请求参数类型
            VelocityTool.buildParamProperty(method, item);
            return item;
        }
        return null;
    }
}
